package bancosys.tec.services.implementor.annotations.visitor;

import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Type;
import java.util.Collection;
import java.util.Collections;

import jmine.tec.utils.annotation.visitor.AnnotationVisitorAdapter;

/**
 * Extende {@link AnnotationVisitorAdapter} para conhecer a nocao de setter, aceitando metodos e parametros anotados com a anotacao passada
 * no construtor
 * 
 * @author takeshi
 * @param <R> o tipo do resultado
 */
public abstract class SetterAndParametersAnnotationVisitor<R> extends AnnotationVisitorAdapter<R> {

    private final Class<? extends Annotation> annotationType;

    /**
     * C'tor
     * 
     * @param annotationType o tipo da anotacao
     */
    public SetterAndParametersAnnotationVisitor(Class<? extends Annotation> annotationType) {
        super();
        this.annotationType = annotationType;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean acceptsMethod(Annotation ann, Method method, Class<?> declaring) {
        return annotationType.isInstance(ann) && method.getParameterTypes().length == 1 && !Modifier.isStatic(method.getModifiers())
                && !method.isBridge();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean acceptsMethodParameter(Annotation annotation, Class<?> declaring, Method method, int parameterIndex,
            Class<?> paramterType, Type genericType) {
        return annotationType.isInstance(annotation) && !Modifier.isStatic(method.getModifiers());
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean acceptsConstructorParameter(Annotation annotation, Class<?> declaring, Constructor<?> ctor, int parameterIndex,
            Class<?> paramterType, Type genericType) {
        return annotationType.isInstance(annotation);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public final Collection<? extends R> onMethodAnnotation(Annotation ann, Class<?> declared, Method method) {
        Type type = method.getGenericParameterTypes()[0];
        Class<?> paramType = method.getParameterTypes()[0];
        return onPropertySetterAnnotation(ann, declared, getPropertyNameForSetter(method), paramType, type);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public final Collection<? extends R> onFieldAnnotation(Annotation annotation, Class<?> declaringClass, Field field) {
        return onPropertySetterAnnotation(annotation, declaringClass, field.getName(), field.getType(), field.getGenericType());
    }

    /**
     * Trata de maneira uniforme tanto setters quanto campos
     * 
     * @param annotation a {@link Annotation}
     * @param declaringClass a classe declarante
     * @param propertyName o nome da propriedade
     * @param propertyRawType o tipo da propriedade
     * @param generictType a declaracao generica da propriedade
     * @return {@link Collection} de R
     */
    protected Collection<? extends R> onPropertySetterAnnotation(Annotation annotation, Class<?> declaringClass, String propertyName,
            Class<?> propertyRawType, Type generictType) {
        return Collections.emptyList();
    }

    /**
     * Devolve o nome da propriedade para um metodo setter ou devolve o nome do metodo caso contrario
     * 
     * @param method o {@link Method}
     * @return {@link String}
     */
    public static String getPropertyNameForSetter(Method method) {
        final int setPreffix = 3;
        if (method.getName().startsWith("set") && method.getName().length() > setPreffix) {
            String sub = method.getName().substring(setPreffix);
            return Character.toLowerCase(sub.charAt(0)) + sub.substring(1);
        }
        return method.getName();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean acceptsFieldAnnotation(Annotation annotation, Class<?> declaringClass, Field field) {
        return !Modifier.isStatic(field.getModifiers()) && !Modifier.isFinal(field.getModifiers())
                && this.annotationType.isInstance(annotation);
    }

}
